package com.googlecode.mailnet.emf.netmodel.simulation;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import com.googlecode.mailnet.info.ILocation;
import com.googlecode.mailnet.info.ILinkInfo;
import com.googlecode.mailnet.info.IMessageInfo;
import com.googlecode.mailnet.info.IMessageListener;
import com.googlecode.mailnet.info.INodeInfo;
import com.googlecode.mailnet.info.ISimulationInfo;
import com.googlecode.mailnet.info.MessageFailureType;
import com.googlecode.mailnet.netmodel.Link;
import com.googlecode.mailnet.netmodel.Node;

public class SimulationStatistics implements IMessageListener {
	
	@Override
	public void onMessageCreated(IMessageInfo message) {
		messagesCreated++;
	}

	@Override
	public void onMessageDelivered(IMessageInfo message) {
		messagedDelivered++;
	}

	@Override
	public void onMessageFailed(IMessageInfo message, MessageFailureType failureType, ILocation location) {
		// System.out.println("failed");
		if (location instanceof ILinkInfo) {
			ILinkInfo linkInfo = (ILinkInfo)location;
			if (failureType == MessageFailureType.QUEUE_OVERFLOW) {
				Node node = linkInfo.getLink().getSource();
				getNodeStats(node).dataFailed += message.size();
				getNodeStats(node).queueOverflowFaults++;
			} else {
				getLinkStats(linkInfo.getLink()).dataFailed += message.size();
			}
		} else if (location instanceof INodeInfo) {
			INodeInfo nodeInfo = (INodeInfo)location;
			getNodeStats(nodeInfo.getNode()).dataFailed += message.size();
		}
	}

	@Override
	public void onMessageTransmitted(IMessageInfo message, ILocation location) {
		// System.out.println("transmitted");
		if (location instanceof ILinkInfo) {
			ILinkInfo linkInfo = (ILinkInfo)location;
			getLinkStats(linkInfo.getLink()).dataTransmitted += message.size();
		} else if (location instanceof INodeInfo) {
			INodeInfo nodeInfo = (INodeInfo)location;
			//if (nodeInfo.getNode() != message.getMessageFlow().getSource())
			getNodeStats(nodeInfo.getNode()).dataTransmitted += message.size();
		}
	}

	final ISimulationInfo simulationInfo;
	
	int messagesCreated = 0;
	
	public int getMessagesCreated() {
		return messagesCreated;
	}

	int messagedDelivered = 0;
	
	public int getMessagedDelivered() {
		return messagedDelivered;
	}

	Map<Link, LinkStatistics> linkStats = new HashMap<Link, LinkStatistics>();
	Map<Node, NodeStatistics> nodeStats = new HashMap<Node, NodeStatistics>();
	
	public SimulationStatistics(ISimulationInfo info) {
		this.simulationInfo = info;
		info.addMessageListener(this);
		
		Iterator<Link> it = info.getLinkInfoSet().keySet().iterator();
		double maxLinkFailureProbability = 0.0;
		while (it.hasNext()) {
			Link l = it.next();
			linkStats.put(l, new LinkStatistics());
			if (l.getFailureProbability() > maxLinkFailureProbability) {
				maxLinkFailureProbability = l.getFailureProbability();
			}
		}
		
		if (maxLinkFailureProbability > 0) {
			it = info.getLinkInfoSet().keySet().iterator();
			while (it.hasNext()) {
				Link l = it.next();
				linkStats.get(l).normalizedFailureProbability = l.getFailureProbability() / maxLinkFailureProbability;
			}
		}
		
		Iterator<Node> n_it = info.getNodeInfoSet().keySet().iterator();
		double maxNodeFailureProbability = 0.0;
		while (n_it.hasNext()) {
			Node n = n_it.next();
			nodeStats.put(n, new NodeStatistics());
			if (n.getFailureProbability() > maxNodeFailureProbability) {
				maxNodeFailureProbability = n.getFailureProbability();
			}
		}
		
		if (maxNodeFailureProbability > 0) {
			n_it = info.getNodeInfoSet().keySet().iterator();
			while (n_it.hasNext()) {
				Node n = n_it.next();
				nodeStats.get(n).normalizedFailureProbability = n.getFailureProbability() / maxNodeFailureProbability;
			}
		}
	}
	
	public LinkStatistics getLinkStats(Link link) {
		return linkStats.get(link);
	}
	
	public NodeStatistics getNodeStats(Node node) {
		return nodeStats.get(node);
	}
	
	public static final int IMPORTANCE_COUNT = 10;
	
	public void evaluateStep() {
		Iterator<Link> it = linkStats.keySet().iterator();
		double totalDataToTransmit = 0.0;
		while (it.hasNext()) {
			Link link = it.next();
			ILinkInfo linkInfo = simulationInfo.getLinkInfoSet().get(link);
			totalDataToTransmit += linkInfo.getAttemptedToTransferPerStep();
		}
		
		// int stepCount = simulationInfo.now() > IMPORTANCE_COUNT ? IMPORTANCE_COUNT : simulationInfo.now();
		
		double maxImportance = 0.0;
		double minImportance = 1.0;
		
		it = linkStats.keySet().iterator();
		while (it.hasNext()) {
			Link link = it.next();
			ILinkInfo linkInfo = simulationInfo.getLinkInfoSet().get(link);
			// LinkStatistics stat = linkStats.get(link);
			double dataToTransmit = linkInfo.getAttemptedToTransferPerStep();
			double newImportance = dataToTransmit / totalDataToTransmit;
			//double newImportance = (stat.importance * (stepCount - 1) + newImportanceStep) / stepCount;
			if (newImportance > maxImportance) maxImportance = newImportance;
			if (newImportance < minImportance) minImportance = newImportance;
		}
		
		it = linkStats.keySet().iterator();
		while (it.hasNext()) {
			Link link = it.next();
			ILinkInfo linkInfo = simulationInfo.getLinkInfoSet().get(link);
			LinkStatistics stat = linkStats.get(link);
			
			// check timeLinkIsFull
			stat.updateFull(linkInfo.getUsageValue() >= 1.0, simulationInfo.now());
			
			// calculate average usage
			Double prevValue = stat.totalAverageUsage;
			if (simulationInfo.now() == 0) continue;
			stat.totalAverageUsage = (prevValue * (simulationInfo.now() - 1) + simulationInfo.getLinkInfoSet().get(link).getUsageValue()) / simulationInfo.now();
		
			// calculate on alive average usage
			if (!linkInfo.isBroken()) {
				prevValue = stat.onAliveAverageUsage;
				stat.onAliveAverageUsage = (prevValue * stat.timeLinkIsAlive + prevValue) / (stat.timeLinkIsFull + 1);
				stat.timeLinkIsAlive++;
			}
			
			// calculate importance

			double dataToTransmit = linkInfo.getAttemptedToTransferPerStep();
			
			double newImportance = dataToTransmit / totalDataToTransmit;
			double newImportanceNormalized = (newImportance - minImportance) / (maxImportance - minImportance);
			stat.updateImportance(newImportanceNormalized, simulationInfo.now());
		}
		
		// calculate node importance 
		totalDataToTransmit = 0.0;
		Iterator<Node> n_it = nodeStats.keySet().iterator();
		while (n_it.hasNext()) {
			Node node = n_it.next();
			INodeInfo nodeInfo = simulationInfo.getNodeInfoSet().get(node);
			totalDataToTransmit += nodeInfo.getAttemptedToTransferPerStep();
		}
		
		maxImportance = 0.0;
		minImportance = 1.0;
		
		n_it = nodeStats.keySet().iterator();
		while (n_it.hasNext()) {
			Node node = n_it.next();
			INodeInfo nodeInfo = simulationInfo.getNodeInfoSet().get(node);
			// NodeStatistics stat = nodeStats.get(node);
			double dataToTransmit = nodeInfo.getAttemptedToTransferPerStep();
			double newImportance = dataToTransmit / totalDataToTransmit;
			//double newImportance = (stat.importance * (stepCount - 1) + newImportanceStep) / stepCount;
			if (newImportance > maxImportance) maxImportance = newImportance;
			if (newImportance < minImportance) minImportance = newImportance;
		}
		
		n_it = nodeStats.keySet().iterator();
		while (n_it.hasNext()) {
			Node node = n_it.next();
			INodeInfo nodeInfo = simulationInfo.getNodeInfoSet().get(node);
			NodeStatistics stat = nodeStats.get(node);
			
			double dataToTransmit = nodeInfo.getAttemptedToTransferPerStep();
			
			double newImportance = dataToTransmit / totalDataToTransmit;
			double newImportanceNormalized = (newImportance - minImportance) / (maxImportance - minImportance);
			stat.updateImportance(newImportanceNormalized, simulationInfo.now());
		}
	}
	
	
}
